#include "ResPkgFile.h"
#include <zlib.h>
#if defined(_WIN32)
#include <Windows.h>
#include <tchar.h>
#else
#if !defined(INT8)
typedef unsigned char       INT8;
#endif
#if !defined(_T)
#define _T(x)               L ## x
#endif
#if !defined(DWORD)
typedef unsigned long       DWORD;
#endif
#if !defined(far)
#define far
#endif
#if !defined(LPVOID)
typedef void far* LPVOID;
#endif
#if !defined(UINT)
typedef unsigned int        UINT;
#endif
#endif

static INT8 signals[4] = { 'R', 'P', 'F', ' ' };

class CResPkgFile::ThisImpl
{
public:
    std::wstring m_strPath;
    std::vector<FileUnit> m_vFiles;
    std::map<std::wstring, UINT32> m_mFilesIndices;
};

CResPkgFile* CResPkgFile::m_pInstance = nullptr;

CResPkgFile::CResPkgFile()
{
}

CResPkgFile::~CResPkgFile()
{
}

CResPkgFile* CResPkgFile::Handle()
{
    if (!m_pInstance)
    {
        m_pInstance = new CResPkgFile();
        m_pInstance->m_pThis = new ThisImpl;
    }
    return m_pInstance;
}

void CResPkgFile::Release()
{
    if (m_pInstance)
    {
        delete m_pInstance->m_pThis;
        m_pInstance->m_pThis = nullptr;
        delete m_pInstance;
        m_pInstance = nullptr;
    }
}

bool CResPkgFile::IsChanged()
{
    return m_bChanged;
}

const std::wstring& CResPkgFile::GetPath()
{
    return m_pThis->m_strPath;
}

void CResPkgFile::SetPath(const std::wstring& strPath)
{
    m_pThis->m_strPath = strPath;
}

const std::vector<CResPkgFile::FileUnit>& CResPkgFile::GetFiles()
{
    return m_pThis->m_vFiles;
}

const std::map<std::wstring, UINT32>& CResPkgFile::GetFilesIndices()
{
    return m_pThis->m_mFilesIndices;
}

void CResPkgFile::New()
{
    m_bChanged = false;
    m_pThis->m_strPath = _T("");
    m_pThis->m_vFiles.clear();
    m_pThis->m_mFilesIndices.clear();
}

void CResPkgFile::Open_Threaded(const std::wstring& strPath, const std::function<void(int, int, int)>& f)
{
    typedef struct _ThreadParam
    {
        CResPkgFile* pRPF;
        std::wstring strPath;
        std::function<void(int, int, int)> func;
    } ThreadParam;
    DWORD dwThreadId;
    auto OpenRPFThread = [](LPVOID lpParam)->DWORD {
        ThreadParam* ptp = (ThreadParam*)lpParam;

        FILE* fp = _wfopen(ptp->strPath.c_str(), _T("rb"));
        fseek(fp, 0, SEEK_END);
        UINT32 fs = UINT32(ftell(fp));
        fseek(fp, 0, SEEK_SET);
        INT8 _signals[4];
        fread(_signals, sizeof(INT8), 4, fp);
        if (memcmp(_signals, signals, 4) != 0)
        {
            fclose(fp);
            ptp->func(1, 0, 0);
            delete ptp;
            ptp = nullptr;
            return 0;
        }
        UINT32 count = 0;
        fread(&count, sizeof(UINT32), 1, fp);
        uLong ulDstLen = 0;
        fread(&ulDstLen, sizeof(UINT32), 1, fp);
        std::vector<UINT8> vDst(size_t(ulDstLen), 0);
        fread(&vDst[0], sizeof(UINT8), ulDstLen, fp);
        uLongf ulfSrcLen = MAX_FILES * LINE_LEN;
        std::vector<UINT8> vSrc(size_t(MAX_FILES * LINE_LEN), 0);
        uncompress2(&vSrc[0], &ulfSrcLen, &vDst[0], &ulDstLen);
        UINT32 nBodyLen = fs - UINT32(sizeof(UINT32) * 3) - ulDstLen;
        std::vector<UINT8> vBody(size_t(fs), 0);
        fread(&vBody[0], sizeof(UINT8), nBodyLen, fp);
        ptp->pRPF->m_pThis->m_vFiles.clear();
        for (UINT32 i = 0; i < count; i++)
        {
            UINT32 nSrcLen = *((UINT32*)&vSrc[i * LINE_LEN + LEN_SIZE * 0]);
            UINT32 nDstLen = *((UINT32*)&vSrc[i * LINE_LEN + LEN_SIZE * 1]);
            UINT32 pos = *((UINT32*)&vSrc[i * LINE_LEN + LEN_SIZE * 2]);
            int nLevel = *((int*)&vSrc[i * LINE_LEN + LEN_SIZE * 3]);
            std::wstring strSubName((wchar_t*)&vSrc[i * LINE_LEN + LEN_SIZE * 4]);
            std::vector<UINT8> vBodyDst(&vBody[pos], &vBody[pos] + nDstLen);
            std::vector<UINT8> vBodySrc(size_t(nSrcLen), 0);
            uLongf nBodySrcLen = nSrcLen;
            uLong nBodyDstLen = nDstLen;
            uncompress2(&vBodySrc[0], &nBodySrcLen, &vBodyDst[0], &nBodyDstLen);
            FileUnit fu = { 0, strSubName, nLevel, vBodySrc, vBodyDst };
            ptp->pRPF->m_pThis->m_vFiles.push_back(fu);
            ptp->pRPF->m_pThis->m_mFilesIndices[strSubName] = UINT32(ptp->pRPF->m_pThis->m_vFiles.size() - 1);

            ptp->func(2, i + 1, count);
        }
        fclose(fp);
        ptp->pRPF->m_pThis->m_strPath = ptp->strPath;
        ptp->pRPF->m_bChanged = false;

        ptp->func(0, 0, 0);

        delete ptp;
        ptp = nullptr;
        return 0;
    };
    ThreadParam* ptp = new ThreadParam;
    ptp->pRPF = this;
    ptp->strPath = strPath.c_str();
    ptp->func = f;
#if defined(_WIN32)
    CreateThread(nullptr, 0, OpenRPFThread, ptp, 0, &dwThreadId);
#else
    OpenRPFThread(ptp);
    delete ptp;
    ptp = nullptr;
#endif
}

void CResPkgFile::Open(const std::wstring& strPath)
{
    typedef struct _ThreadParam
    {
        CResPkgFile* pRPF;
        std::wstring strPath;
    } ThreadParam;
    ThreadParam tp;
    tp.pRPF = this;
    tp.strPath = strPath;
    ThreadParam* ptp = (ThreadParam*)&tp;//lpParam;

    FILE* fp = _wfopen(ptp->strPath.c_str(), _T("rb"));
    fseek(fp, 0, SEEK_END);
    UINT32 fs = UINT32(ftell(fp));
    fseek(fp, 0, SEEK_SET);
    INT8 _signals[4];
    fread(_signals, sizeof(INT8), 4, fp);
    if (memcmp(_signals, signals, 4) != 0)
    {
        fclose(fp);
        return;
    }
    UINT32 count = 0;
    fread(&count, sizeof(UINT32), 1, fp);
    uLong ulDstLen = 0;
    fread(&ulDstLen, sizeof(UINT32), 1, fp);
    std::vector<UINT8> vDst(size_t(ulDstLen), 0);
    fread(&vDst[0], sizeof(UINT8), ulDstLen, fp);
    uLongf ulfSrcLen = MAX_FILES * LINE_LEN;
    std::vector<UINT8> vSrc(size_t(MAX_FILES * LINE_LEN), 0);
    uncompress2(&vSrc[0], &ulfSrcLen, &vDst[0], &ulDstLen);
    UINT32 nBodyLen = fs - UINT32(sizeof(UINT32) * 3) - ulDstLen;
    std::vector<UINT8> vBody(size_t(fs), 0);
    fread(&vBody[0], sizeof(UINT8), nBodyLen, fp);
    ptp->pRPF->m_pThis->m_vFiles.clear();
    for (UINT32 i = 0; i < count; i++)
    {
        UINT32 nSrcLen = *((UINT32*)&vSrc[i * LINE_LEN + LEN_SIZE * 0]);
        UINT32 nDstLen = *((UINT32*)&vSrc[i * LINE_LEN + LEN_SIZE * 1]);
        UINT32 pos = *((UINT32*)&vSrc[i * LINE_LEN + LEN_SIZE * 2]);
        int nLevel = *((int*)&vSrc[i * LINE_LEN + LEN_SIZE * 3]);
        std::wstring strSubName((wchar_t*)&vSrc[i * LINE_LEN + LEN_SIZE * 4]);
        std::vector<UINT8> vBodyDst(&vBody[pos], &vBody[pos] + nDstLen);
        std::vector<UINT8> vBodySrc(size_t(nSrcLen), 0);
        uLongf nBodySrcLen = nSrcLen;
        uLong nBodyDstLen = nDstLen;
        uncompress2(&vBodySrc[0], &nBodySrcLen, &vBodyDst[0], &nBodyDstLen);
        FileUnit fu = { 0, strSubName, nLevel, vBodySrc, vBodyDst };
        ptp->pRPF->m_pThis->m_vFiles.push_back(fu);
        ptp->pRPF->m_pThis->m_mFilesIndices[strSubName] = UINT32(ptp->pRPF->m_pThis->m_vFiles.size() - 1);
    }
    fclose(fp);
    ptp->pRPF->m_pThis->m_strPath = ptp->strPath;
    ptp->pRPF->m_bChanged = false;
    return;
}

void CResPkgFile::Close()
{
    m_bChanged = false;
    m_pThis->m_strPath = _T("");
    m_pThis->m_vFiles.clear();
    m_pThis->m_mFilesIndices.clear();
}

void CResPkgFile::Save()
{
    FILE* fp = _wfopen(m_pThis->m_strPath.c_str(), _T("wb"));
    std::vector<UINT8> vHeadSrc(size_t(MAX_FILES * LINE_LEN));
    std::vector<UINT8> vBody;
    UINT32 pos = 0;
    for (size_t i = 0; i < m_pThis->m_vFiles.size(); i++)
    {
        UINT32 nSrcLen = UINT32(m_pThis->m_vFiles[i].vSource.size());
        memcpy(&vHeadSrc[i * LINE_LEN + LEN_SIZE * 0], (UINT8*)&nSrcLen, sizeof(UINT32));
        UINT32 nDstLen = UINT32(m_pThis->m_vFiles[i].vCompressed.size());
        memcpy(&vHeadSrc[i * LINE_LEN + LEN_SIZE * 1], (UINT8*)&nDstLen, sizeof(UINT32));
        memcpy(&vHeadSrc[i * LINE_LEN + LEN_SIZE * 2], &pos, sizeof(UINT32));
        int nLevel = m_pThis->m_vFiles[i].nLevel;
        memcpy(&vHeadSrc[i * LINE_LEN + LEN_SIZE * 3], &nLevel, sizeof(int));
        memcpy(&vHeadSrc[i * LINE_LEN + LEN_SIZE * 4], m_pThis->m_vFiles[i].strSubName.c_str(), m_pThis->m_vFiles[i].strSubName.size() * sizeof(wchar_t));
        vBody.insert(vBody.end(), m_pThis->m_vFiles[i].vCompressed.data(), m_pThis->m_vFiles[i].vCompressed.data() + m_pThis->m_vFiles[i].vCompressed.size());
        pos += UINT32(m_pThis->m_vFiles[i].vCompressed.size());
    }
    uLong ulDstLen = compressBound(uLong(vHeadSrc.size()));
    std::vector<UINT8> vDst(size_t(ulDstLen), 0);
    compress2(&vDst[0], &ulDstLen, vHeadSrc.data(), uLong(vHeadSrc.size()), Z_BEST_COMPRESSION);
    fwrite(signals, sizeof(INT8), 4, fp);
    UINT32 count = UINT32(m_pThis->m_vFiles.size());
    fwrite(&count, sizeof(UINT32), 1, fp);
    fwrite(&ulDstLen, sizeof(UINT32), 1, fp);
    fwrite(&vDst[0], sizeof(UINT8), ulDstLen, fp);
    fwrite(vBody.data(), sizeof(UINT8), UINT(vBody.size()), fp);
    fclose(fp);
    m_bChanged = false;
}

int CResPkgFile::Insert(const std::wstring& strPath, int level, const std::wstring& strSubName, bool force)
{
    auto funcInsert = [](const std::wstring& strPath, int level, const std::wstring& strSubName, bool force, std::vector<FileUnit>& v, std::map<std::wstring, UINT32>& m)->void {
        FileUnit fu = { 0, strSubName, level, {}, {} };
        FILE* fp = _wfopen(strPath.c_str(), _T("rb"));
        fseek(fp, 0, SEEK_END);
        long fs = ftell(fp);
        fseek(fp, 0, SEEK_SET);
        if (fs > 0)
        {
            fu.vSource.resize(size_t(fs), 0);
            fread(&fu.vSource[0], sizeof(UINT8), UINT(fs), fp);
            uLong ulDstLen = compressBound(uLong(fu.vSource.size()));
            std::vector<UINT8> vDst(size_t(ulDstLen), 0);
            compress2(&vDst[0], &ulDstLen, fu.vSource.data(), uLong(fu.vSource.size()), level);
            fu.vCompressed.insert(fu.vCompressed.end(), &vDst[0], &vDst[0] + ulDstLen);
        }
        fclose(fp);
        v.push_back(fu);
        m[strSubName] = UINT32(v.size() - 1);
    };
    if (force)
        funcInsert(strPath.c_str(), level, strSubName, force, m_pThis->m_vFiles, m_pThis->m_mFilesIndices);
    else
    {
        if (0 == m_pThis->m_mFilesIndices.count(strSubName))
            funcInsert(strPath.c_str(), level, strSubName, force, m_pThis->m_vFiles, m_pThis->m_mFilesIndices);
        else
            return 1;
    }
    m_bChanged = true;
    return 0;
}

void CResPkgFile::Edit(const std::wstring& strOldSubName, int level, const std::wstring& strSubName)
{
    UINT32 index = m_pThis->m_mFilesIndices[strOldSubName];
    m_pThis->m_vFiles[index].nLevel = level;
    m_pThis->m_vFiles[index].strSubName = strSubName;
    m_pThis->m_vFiles[index].vCompressed.clear();
    uLong ulDstLen = compressBound(uLong(m_pThis->m_vFiles[index].vSource.size()));
    std::vector<UINT8> vDst(size_t(ulDstLen), 0);
    compress2(&vDst[0], &ulDstLen, m_pThis->m_vFiles[index].vSource.data(), uLong(m_pThis->m_vFiles[index].vSource.size()), level);
    m_pThis->m_vFiles[index].vCompressed.insert(m_pThis->m_vFiles[index].vCompressed.end(), &vDst[0], &vDst[0] + ulDstLen);
    m_bChanged = true;
}

void CResPkgFile::Delete(const std::wstring& strSubName)
{
    if (m_pThis->m_mFilesIndices.count(strSubName) != 0)
    {
        UINT32 index = m_pThis->m_mFilesIndices[strSubName];
        m_pThis->m_vFiles.erase(m_pThis->m_vFiles.begin() + index);
        for (auto iter = m_pThis->m_mFilesIndices.begin(); iter != m_pThis->m_mFilesIndices.end();)
            if (iter->first == strSubName)
                m_pThis->m_mFilesIndices.erase(iter++);
            else
                iter++;
        for (auto& p : m_pThis->m_mFilesIndices)
            if (p.second > index)
                p.second--;
        m_bChanged = true;
    }
}